package com.andrognito.patternlockview;


import com.andrognito.patternlockview.listener.PatternLockViewListener;
import com.andrognito.patternlockview.utils.*;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.miscservices.timeutility.Time;
import ohos.multimodalinput.event.TouchEvent;

import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;

import static com.andrognito.patternlockview.PatternLockView.AspectRatio.ASPECT_RATIO_HEIGHT_BIAS;
import static com.andrognito.patternlockview.PatternLockView.AspectRatio.ASPECT_RATIO_SQUARE;
import static com.andrognito.patternlockview.PatternLockView.AspectRatio.ASPECT_RATIO_WIDTH_BIAS;
import static com.andrognito.patternlockview.PatternLockView.PatternViewMode.AUTO_DRAW;
import static com.andrognito.patternlockview.PatternLockView.PatternViewMode.CORRECT;
import static com.andrognito.patternlockview.PatternLockView.PatternViewMode.WRONG;
import static java.lang.Math.max;

/**
 * Displays a powerful, customizable and Material Design complaint pattern lock in the screen which
 * can be used to lock any Activity or Fragment from the user
 */
public class PatternLockView extends Component implements Component.DrawTask, Component.TouchEventListener {


    /**
     * Represents the aspect ratio for the View
     */
//    @IntDef({ASPECT_RATIO_SQUARE, ASPECT_RATIO_WIDTH_BIAS, ASPECT_RATIO_HEIGHT_BIAS})
    @Retention(RetentionPolicy.SOURCE)
    public @interface AspectRatio {
        // Width and height will be same. Minimum of width and height
        int ASPECT_RATIO_SQUARE = 0;
        // Width will be fixed. The height will be the minimum of width and height
        int ASPECT_RATIO_WIDTH_BIAS = 1;
        // Height will be fixed. The width will be the minimum of width and height
        int ASPECT_RATIO_HEIGHT_BIAS = 2;
    }

    /**
     * Represents the different modes in which this view can be represented
     */
//    @IntDef({CORRECT, AUTO_DRAW, WRONG})
    @Retention(RetentionPolicy.SOURCE)
    public @interface PatternViewMode {
        /**
         * This state represents a correctly drawn pattern by the user. The color of the path and
         * the dots both would be changed to this color.
         * <p>
         * (NOTE - Consider showing this state in a friendly color)
         */
        int CORRECT = 0;
        /**
         * ·
         * Automatically draw the pattern for demo or tutorial purposes.
         */
        int AUTO_DRAW = 1;
        /**
         * This state represents a wrongly drawn pattern by the user. The color of the path and
         * the dots both would be changed to this color.
         * <p>
         * (NOTE - Consider showing this state in an attention-seeking color)
         */
        int WRONG = 2;
    }

    private static final int DEFAULT_PATTERN_DOT_COUNT = 3;
    private static final boolean PROFILE_DRAWING = false;

    /**
     * The time (in millis) spend in animating each circle of a lock pattern if
     * the animating mode is set. The entire animation should take this constant
     * the length of the pattern to complete.
     */
    private static final int MILLIS_PER_CIRCLE_ANIMATING = 700;

    // Amount of time (in millis) spent to animate a dot
    private static final int DEFAULT_DOT_ANIMATION_DURATION = 190;
    // Amount of time (in millis) spent to animate a path ends
    private static final int DEFAULT_PATH_END_ANIMATION_DURATION = 100;
    // This can be used to avoid updating the display for very small motions or noisy panels
    private static final float DEFAULT_DRAG_THRESHOLD = 0.0f;

    private DotState[][] mDotStates;
    private int mPatternSize;
    private boolean mDrawingProfilingStarted = false;
    private long mAnimatingPeriodStart;
    private float mHitFactor = 0.6f;

    // Made static so that the static inner class can use it
    private static int sDotCount;

    private boolean mAspectRatioEnabled;
    private int mAspectRatio;
    private int mNormalStateColor;
    private int mWrongStateColor;
    private int mCorrectStateColor;
    private int mPathWidth;
    private int mDotNormalSize;
    private int mDotSelectedSize;
    private int mDotAnimationDuration;
    private int mPathEndAnimationDuration;

    private Paint mDotPaint;
    private Paint mPathPaint;

    private List<PatternLockViewListener> mPatternListeners;
    // The pattern represented as a list of connected {@link Dot}
    private ArrayList<Dot> mPattern;

    /**
     * Lookup table for the dots of the pattern we are currently drawing.
     * This will be the dots of the complete pattern unless we are animating,
     * in which case we use this to hold the dots we are drawing for the in
     * progress animation.
     */
    private boolean[][] mPatternDrawLookup;

    private float mInProgressX = -1;
    private float mInProgressY = -1;

    private int mPatternViewMode = CORRECT;
    private boolean mInputEnabled = true;
    private boolean mInStealthMode = false;
    private boolean mEnableHapticFeedback = true;
    private boolean mPatternInProgress = false;
    private float mViewWidth;
    private float mViewHeight;
    private final Path mCurrentPath = new Path();
    private final Rect mInvalidate = new Rect();
    private final Rect mTempInvalidateRect = new Rect();
    private boolean hasDraw;
    private int screenHeight, screenWidth;
    private int diffY = -50;
    private int diffX = -50;
    private int diffYMove = -260;
    private int diffXMove = -50;

    private static final String dotCount = "dotCount";
    private static final String aspectRatioEnabled = "aspectRatioEnabled";
    private static final String aspectRatio = "aspectRatio";
    private static final String pathWidth = "pathWidth";
    private static final String normalStateColor = "normalStateColor";
    private static final String correctStateColor = "correctStateColor";
    private static final String wrongStateColor = "wrongStateColor";
    private static final String dotNormalSize = "dotNormalSize";
    private static final String dotSelectedSize = "dotSelectedSize";
    private static final String dotAnimationDuration = "dotAnimationDuration";
    private static final String pathEndAnimationDuration = "pathEndAnimationDuration";
    private boolean touchUp;


    public PatternLockView(Context context) {
        this(context, null);
    }

    public PatternLockView(Context context, AttrSet attrs) {
        super(context, attrs);
        sDotCount = AttrUtils.getIntFromAttr(attrs, dotCount, DEFAULT_PATTERN_DOT_COUNT);
        mAspectRatioEnabled = AttrUtils.getBooleanFromAttr(attrs, aspectRatioEnabled, false);
        String aspectRatioAttr = AttrUtils.getStringFromAttr(attrs, aspectRatio, "square");
        mPathWidth = AttrUtils.getDimensionFromAttr(attrs, pathWidth, 12);
        mNormalStateColor = AttrUtils.getColorFromAttr(attrs, normalStateColor, 0xffffffff);
        mCorrectStateColor = AttrUtils.getColorFromAttr(attrs, correctStateColor, 0xffffffff);
        mWrongStateColor = AttrUtils.getColorFromAttr(attrs, wrongStateColor, 0xfff4511e);
        mDotNormalSize = AttrUtils.getDimensionFromAttr(attrs, dotNormalSize, 15);
        mDotSelectedSize = AttrUtils.getDimensionFromAttr(attrs, dotSelectedSize, 24);
        mDotAnimationDuration = AttrUtils.getIntFromAttr(attrs, dotAnimationDuration, DEFAULT_DOT_ANIMATION_DURATION);
        mPathEndAnimationDuration = AttrUtils.getIntFromAttr(attrs, pathEndAnimationDuration, DEFAULT_PATH_END_ANIMATION_DURATION);

        switch (aspectRatioAttr) {
            case "square":
                mAspectRatio = 0;
                break;
            case "width_bias":
                mAspectRatio = 1;
                break;
            case "height_bias":
                mAspectRatio = 2;
                break;
            default:
                mAspectRatio = 0;
                break;
        }

        // The pattern will always be symmetrical
        mPatternSize = sDotCount * sDotCount;
        mPattern = new ArrayList<>(mPatternSize);
        mPatternDrawLookup = new boolean[sDotCount][sDotCount];

        mDotStates = new DotState[sDotCount][sDotCount];
        for (int i = 0; i < sDotCount; i++) {
            for (int j = 0; j < sDotCount; j++) {
                mDotStates[i][j] = new DotState();
                mDotStates[i][j].mSize = mDotNormalSize;
            }
        }

        mPatternListeners = new ArrayList<>();

        initView();
        addDrawTask(this);
        setTouchEventListener(this);
    }

    private void initView() {
        setClickable(true);

        mPathPaint = new Paint();
        mPathPaint.setAntiAlias(true);
        mPathPaint.setDither(true);
        mPathPaint.setColor(new Color(mNormalStateColor));
        mPathPaint.setStyle(Paint.Style.STROKE_STYLE);
        mPathPaint.setStrokeJoin(Paint.Join.ROUND_JOIN);
        mPathPaint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);
        mPathPaint.setStrokeWidth(mPathWidth);

        mDotPaint = new Paint();
        mDotPaint.setAntiAlias(true);
        mDotPaint.setDither(true);

    }


    protected int getSuggestedMinimumWidth() {
        return (mBackgroundElement == null) ? getMinWidth() : max(getMinWidth(), mBackgroundElement.getMinWidth());
    }

    protected int getSuggestedMinimumHeight() {
        return (mBackgroundElement == null) ? getMinHeight() : max(getMinHeight(), mBackgroundElement.getMinHeight());
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (!hasDraw) {
            screenWidth = DisplayManager.getInstance().getDefaultDisplay(getContext()).get().getAttributes().width;
            screenHeight = DisplayManager.getInstance().getDefaultDisplay(getContext()).get().getAttributes().height;
            int adjustedWidth = component.getWidth() - getPaddingLeft() - getPaddingRight();
            mViewWidth = adjustedWidth / (float) sDotCount;
            int adjustedHeight = component.getHeight() - getPaddingTop() - getPaddingBottom();
            mViewHeight = adjustedHeight / (float) sDotCount;
        }
        ArrayList<Dot> pattern = mPattern;
        int patternSize = pattern.size();
        boolean[][] drawLookupTable = mPatternDrawLookup;

        if (mPatternViewMode == AUTO_DRAW) {
            int oneCycle = (patternSize + 1) * MILLIS_PER_CIRCLE_ANIMATING;
            int spotInCycle = (int) (Time.getRealActiveTime() - mAnimatingPeriodStart)
                    % oneCycle;
            int numCircles = spotInCycle / MILLIS_PER_CIRCLE_ANIMATING;

            clearPatternDrawLookup();
            for (int i = 0; i < numCircles; i++) {
                Dot dot = pattern.get(i);
                drawLookupTable[dot.mRow][dot.mColumn] = true;
            }

            boolean needToUpdateInProgressPoint = numCircles > 0
                    && numCircles < patternSize;

            if (needToUpdateInProgressPoint) {
                float percentageOfNextCircle = ((float) (spotInCycle % MILLIS_PER_CIRCLE_ANIMATING))
                        / MILLIS_PER_CIRCLE_ANIMATING;

                Dot currentDot = pattern.get(numCircles - 1);
                float centerX = getCenterXForColumn(currentDot.mColumn);
                float centerY = getCenterYForRow(currentDot.mRow);

                Dot nextDot = pattern.get(numCircles);
                float dx = percentageOfNextCircle
                        * (getCenterXForColumn(nextDot.mColumn) - centerX);
                float dy = percentageOfNextCircle
                        * (getCenterYForRow(nextDot.mRow) - centerY);
                mInProgressX = centerX + dx;
                mInProgressY = centerY + dy;
            }
            invalidate();
        }

        Path currentPath = mCurrentPath;
        currentPath.rewind();

        LogUtil.error(this.getClass().getName(),"1=====draw===dot===="+sDotCount);
        // Draw the dots
        for (int i = 0; i < sDotCount; i++) {
            float centerY = getCenterYForRow(i);
            for (int j = 0; j < sDotCount; j++) {
                DotState dotState = mDotStates[i][j];
                float centerX = getCenterXForColumn(j);
                float size = dotState.mSize * dotState.mScale;
                float translationY = dotState.mTranslateY;
                drawCircle(canvas, (int) centerX, (int) centerY + translationY,
                        size, drawLookupTable[i][j], dotState.mAlpha);
            }
        }

        // Draw the path of the pattern (unless we are in stealth mode)
        boolean drawPath = !mInStealthMode;
        //TODO 绘制连线
        if (drawPath) {
            mPathPaint.setColor(getCurrentColor(true));

            boolean anyCircles = false;
            float lastX = 0f;
            float lastY = 0f;
            for (int i = 0; i < patternSize; i++) {
                Dot dot = pattern.get(i);

                // Only draw the part of the pattern stored in
                // the lookup table (this is only different in case
                // of animation)
                if (!drawLookupTable[dot.mRow][dot.mColumn]) {
                    break;
                }
                anyCircles = true;

                float centerX = getCenterXForColumn(dot.mColumn);
                float centerY = getCenterYForRow(dot.mRow);
                if (i != 0) {
                    DotState state = mDotStates[dot.mRow][dot.mColumn];
                    currentPath.rewind();
                    currentPath.moveTo(lastX, lastY);
                    if (state.mLineEndX != Float.MIN_VALUE
                            && state.mLineEndY != Float.MIN_VALUE) {
                        currentPath.lineTo(state.mLineEndX, state.mLineEndY);
                    } else {
                        currentPath.lineTo(centerX, centerY);
                    }
                    canvas.drawPath(currentPath, mPathPaint);
                }
                lastX = centerX;
                lastY = centerY;
            }

            // Draw last in progress section
            if ((mPatternInProgress || mPatternViewMode == AUTO_DRAW)
                    && anyCircles) {
                currentPath.rewind();
                currentPath.moveTo(lastX, lastY);
                currentPath.lineTo(mInProgressX, mInProgressY);

                mPathPaint.setAlpha((int) (calculateLastSegmentAlpha(
                        mInProgressX, mInProgressY, lastX, lastY) * 255f));
                canvas.drawPath(currentPath, mPathPaint);
            }
        }

        //TODO 140是从Draw the dots centerY取出的大概距离，因为触摸事件的down和move取出的getY()是不一样的所以去掉的diffY也不一样
        if (!hasDraw) {
            int occHeight = getHeight() + getMarginBottom();
            diffY = -(screenHeight - occHeight - 750);
            diffYMove = -(screenHeight - occHeight - 750);
            hasDraw = true;
        }
    }


    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        if (!mInputEnabled || !isEnabled()) {
            return true;
        }

        switch (event.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                handleActionDown(event);
                return true;
            case TouchEvent.PRIMARY_POINT_UP:
                touchUp=true;
                LogUtil.error(this.getName(),"1========drawCircle====PRIMARY_POINT_UP");
                handleActionUp(event);
                return true;
            case TouchEvent.POINT_MOVE:
                handleActionMove(event);
                return true;
            case TouchEvent.CANCEL:
            case TouchEvent.PHASE_CANCEL:
                mPatternInProgress = false;
                resetPattern();
                notifyPatternCleared();

                if (PROFILE_DRAWING) {
                    if (mDrawingProfilingStarted) {
                        mDrawingProfilingStarted = false;
                    }
                }
                return true;
        }
        return true;
    }


    @SuppressWarnings("unchecked")
    public List<Dot> getPattern() {
        return (List<Dot>) mPattern.clone();
    }

    @PatternViewMode
    public int getPatternViewMode() {
        return mPatternViewMode;
    }

    public boolean isInStealthMode() {
        return mInStealthMode;
    }

    public boolean isTactileFeedbackEnabled() {
        return mEnableHapticFeedback;
    }

    public boolean isInputEnabled() {
        return mInputEnabled;
    }

    public int getDotCount() {
        return sDotCount;
    }

    public boolean isAspectRatioEnabled() {
        return mAspectRatioEnabled;
    }

    @AspectRatio
    public int getAspectRatio() {
        return mAspectRatio;
    }

    public int getNormalStateColor() {
        return mNormalStateColor;
    }

    public int getWrongStateColor() {
        return mWrongStateColor;
    }

    public int getCorrectStateColor() {
        return mCorrectStateColor;
    }

    public int getPathWidth() {
        return mPathWidth;
    }

    public int getDotNormalSize() {
        return mDotNormalSize;
    }

    public int getDotSelectedSize() {
        return mDotSelectedSize;
    }

    public int getPatternSize() {
        return mPatternSize;
    }

    public int getDotAnimationDuration() {
        return mDotAnimationDuration;
    }

    public int getPathEndAnimationDuration() {
        return mPathEndAnimationDuration;
    }

    /**
     * Set the pattern explicitly rather than waiting for the user to input a
     * pattern. You can use this for help or demo purposes
     *
     * @param patternViewMode The mode in which the pattern should be displayed
     * @param pattern         The pattern
     */
    public void setPattern(@PatternViewMode int patternViewMode, List<Dot> pattern) {
        mPattern.clear();
        mPattern.addAll(pattern);
        clearPatternDrawLookup();
        for (Dot dot : pattern) {
            mPatternDrawLookup[dot.mRow][dot.mColumn] = true;
        }
        setViewMode(patternViewMode);
    }


    public void setViewMode(@PatternViewMode int patternViewMode) {
        mPatternViewMode = patternViewMode;
        if (patternViewMode == AUTO_DRAW) {
            if (mPattern.size() == 0) {
                throw new IllegalStateException(
                        "you must have a pattern to "
                                + "animate if you want to set the display mode to animate");
            }
            mAnimatingPeriodStart = Time.getRealActiveTime();
            final Dot first = mPattern.get(0);
            mInProgressX = getCenterXForColumn(first.mColumn);
            mInProgressY = getCenterYForRow(first.mRow);
            clearPatternDrawLookup();
        }
        invalidate();
    }

    public void setDotCount(int dotCount) {
        sDotCount = dotCount;
        mPatternSize = sDotCount * sDotCount;
        mPattern = new ArrayList<>(mPatternSize);
        mPatternDrawLookup = new boolean[sDotCount][sDotCount];

        mDotStates = new DotState[sDotCount][sDotCount];
        for (int i = 0; i < sDotCount; i++) {
            for (int j = 0; j < sDotCount; j++) {
                mDotStates[i][j] = new DotState();
                mDotStates[i][j].mSize = mDotNormalSize;
            }
        }

        invalidate();
    }

    public void setAspectRatioEnabled(boolean aspectRatioEnabled) {
        mAspectRatioEnabled = aspectRatioEnabled;
        invalidate();
    }

    public void setAspectRatio(@AspectRatio int aspectRatio) {
        mAspectRatio = aspectRatio;
        invalidate();
    }

    public void setNormalStateColor(int normalStateColor) {
        mNormalStateColor = normalStateColor;
    }

    public void setWrongStateColor(int wrongStateColor) {
        mWrongStateColor = wrongStateColor;
    }

    public void setCorrectStateColor(int correctStateColor) {
        mCorrectStateColor = correctStateColor;
    }

    public void setPathWidth(int pathWidth) {
        mPathWidth = pathWidth;

        initView();
        invalidate();
    }

    public void setDotNormalSize(int dotNormalSize) {
        mDotNormalSize = dotNormalSize;

        for (int i = 0; i < sDotCount; i++) {
            for (int j = 0; j < sDotCount; j++) {
                mDotStates[i][j] = new DotState();
                mDotStates[i][j].mSize = mDotNormalSize;
            }
        }

        invalidate();
    }

    public void setDotSelectedSize(int dotSelectedSize) {
        mDotSelectedSize = dotSelectedSize;
    }

    public void setDotAnimationDuration(int dotAnimationDuration) {
        mDotAnimationDuration = dotAnimationDuration;
        invalidate();
    }

    public void setPathEndAnimationDuration(int pathEndAnimationDuration) {
        mPathEndAnimationDuration = pathEndAnimationDuration;
    }


    public void setInStealthMode(boolean inStealthMode) {
        mInStealthMode = inStealthMode;
    }

    public void setTactileFeedbackEnabled(boolean tactileFeedbackEnabled) {
        mEnableHapticFeedback = tactileFeedbackEnabled;
    }


    public void setInputEnabled(boolean inputEnabled) {
        mInputEnabled = inputEnabled;
    }

    public void setEnableHapticFeedback(boolean enableHapticFeedback) {
        mEnableHapticFeedback = enableHapticFeedback;
    }

    public void addPatternLockListener(PatternLockViewListener patternListener) {
        mPatternListeners.add(patternListener);
    }

    public void removePatternLockListener(PatternLockViewListener patternListener) {
        mPatternListeners.remove(patternListener);
    }

    public void clearPattern() {
        resetPattern();
    }

    private void notifyPatternProgress() {
        sendAccessEvent(ResourceTable.String_message_pattern_dot_added);
        notifyListenersProgress(mPattern);
    }

    private void notifyPatternStarted() {
        sendAccessEvent(ResourceTable.String_message_pattern_started);
        notifyListenersStarted();
    }

    private void notifyPatternDetected() {
        sendAccessEvent(ResourceTable.String_message_pattern_detected);
        notifyListenersComplete(mPattern);
    }

    private void notifyPatternCleared() {
        sendAccessEvent(ResourceTable.String_message_pattern_cleared);
        notifyListenersCleared();
    }

    private void resetPattern() {
        hasDraw = false;
        mPattern.clear();
        clearPatternDrawLookup();
        mPatternViewMode = CORRECT;
        invalidate();
    }

    private void notifyListenersStarted() {
        for (PatternLockViewListener patternListener : mPatternListeners) {
            if (patternListener != null) {
                patternListener.onStarted();
            }
        }
    }

    private void notifyListenersProgress(List<Dot> pattern) {
        for (PatternLockViewListener patternListener : mPatternListeners) {
            if (patternListener != null) {
                patternListener.onProgress(pattern);
            }
        }
    }

    private void notifyListenersComplete(List<Dot> pattern) {
        for (PatternLockViewListener patternListener : mPatternListeners) {
            if (patternListener != null) {
                patternListener.onComplete(pattern);
            }
        }
    }

    private void notifyListenersCleared() {
        for (PatternLockViewListener patternListener : mPatternListeners) {
            if (patternListener != null) {
                patternListener.onCleared();
            }
        }
    }

    private void clearPatternDrawLookup() {
        for (int i = 0; i < sDotCount; i++) {
            for (int j = 0; j < sDotCount; j++) {
                mPatternDrawLookup[i][j] = false;
            }
        }
    }


    private Dot detectAndAddHit(float x, float y) {
        final Dot dot = checkForNewHit(x, y);
        if (dot != null) {
            // Check for gaps in existing pattern
            Dot fillInGapDot = null;
            final ArrayList<Dot> pattern = mPattern;
            if (!pattern.isEmpty()) {
                Dot lastDot = pattern.get(pattern.size() - 1);
                int dRow = dot.mRow - lastDot.mRow;
                int dColumn = dot.mColumn - lastDot.mColumn;

                int fillInRow = lastDot.mRow;
                int fillInColumn = lastDot.mColumn;

                if (Math.abs(dRow) == 2 && Math.abs(dColumn) != 1) {
                    fillInRow = lastDot.mRow + ((dRow > 0) ? 1 : -1);
                }

                if (Math.abs(dColumn) == 2 && Math.abs(dRow) != 1) {
                    fillInColumn = lastDot.mColumn + ((dColumn > 0) ? 1 : -1);
                }

                fillInGapDot = Dot.of(fillInRow, fillInColumn);
            }

            if (fillInGapDot != null
                    && !mPatternDrawLookup[fillInGapDot.mRow][fillInGapDot.mColumn]) {
                addCellToPattern(fillInGapDot);
            }
            addCellToPattern(dot);
            return dot;
        }
        return null;
    }

    private void addCellToPattern(Dot newDot) {
        LogUtil.error(this.getName(), "1=======addCellToPattern real set====" + newDot);
        mPatternDrawLookup[newDot.mRow][newDot.mColumn] = true;
        mPattern.add(newDot);
        if (!mInStealthMode) {
            startDotSelectedAnimation(newDot);
        }
        notifyPatternProgress();
    }

    private void startDotSelectedAnimation(Dot dot) {
        final DotState dotState = mDotStates[dot.mRow][dot.mColumn];
        startSizeAnimation(mDotNormalSize, mDotSelectedSize, mDotAnimationDuration, dotState, new Runnable() {

            @Override
            public void run() {
                startSizeAnimation(mDotSelectedSize, mDotNormalSize, mDotAnimationDuration, dotState, null);
            }
        });
        startLineEndAnimation(dotState, mInProgressX, mInProgressY,
                getCenterXForColumn(dot.mColumn), getCenterYForRow(dot.mRow));
    }

    private void startLineEndAnimation(final DotState state,
                                       final float startX, final float startY, final float targetX,
                                       final float targetY) {
        AnimatorValue valueAnimator = new AnimatorValue();
        valueAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                float t = v;
                state.mLineEndX = (1 - t) * startX + t * targetX;
                state.mLineEndY = (1 - t) * startY + t * targetY;
                invalidate();
            }
        });
        valueAnimator.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {

            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
                state.mLineAnimator = null;

            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }
        });

//        valueAnimator.setInterpolator(mFastOutSlowInInterpolator);
        valueAnimator.setDuration(mPathEndAnimationDuration);
        valueAnimator.start();
        state.mLineAnimator = valueAnimator;
    }

    private void startSizeAnimation(float start, float end, long duration,
                                    final DotState state,
                                    final Runnable endRunnable) {
        AnimatorValue valueAnimator = new AnimatorValue();
        valueAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                LogUtil.error(this.getClass().getName(), "1========startSizeAnimation===" + v);
                //TODO 两点连线时 放大点的动画
                float tempSize = v * 100;
                if (v == 1) {
                    tempSize = mDotNormalSize;
                }
                state.mSize = tempSize;
                invalidate();
            }
        });

        if (endRunnable != null) {
            valueAnimator.setStateChangedListener(new Animator.StateChangedListener() {
                @Override
                public void onStart(Animator animator) {

                }

                @Override
                public void onStop(Animator animator) {

                }

                @Override
                public void onCancel(Animator animator) {

                }

                @Override
                public void onEnd(Animator animator) {
                    if (endRunnable != null) {
                        endRunnable.run();
                    }
                }

                @Override
                public void onPause(Animator animator) {

                }

                @Override
                public void onResume(Animator animator) {

                }
            });
        }
//        valueAnimator.setInterpolator(interpolator);
        valueAnimator.setDuration(duration);
        valueAnimator.start();
    }


    private Dot checkForNewHit(float x, float y) {
        final int rowHit = getRowHit(y);
        if (rowHit < 0) {
            return null;
        }
        final int columnHit = getColumnHit(x);
        if (columnHit < 0) {
            return null;
        }

        if (mPatternDrawLookup[rowHit][columnHit]) {
            return null;
        }
        return Dot.of(rowHit, columnHit);
    }

    /**
     * Helper method to find the row that y coordinate falls into
     *
     * @param y The y coordinate
     * @return The mRow that y falls in, or -1 if it falls in no mRow
     */
    private int getRowHit(float y) {
        final float squareHeight = mViewHeight;
        float hitSize = squareHeight * mHitFactor;
        float offset = getPaddingTop() + (squareHeight - hitSize) / 2f;
        for (int i = 0; i < sDotCount; i++) {
            float hitTop = offset + squareHeight * i;
            if (y >= hitTop && y <= hitTop + hitSize) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Helper method to find the column x falls into
     *
     * @param x The x coordinate
     * @return The mColumn that x falls in, or -1 if it falls in no mColumn
     */
    private int getColumnHit(float x) {
        final float squareWidth = mViewWidth;
        float hitSize = squareWidth * mHitFactor;

        float offset = getPaddingLeft() + (squareWidth - hitSize) / 2f;
        for (int i = 0; i < sDotCount; i++) {

            final float hitLeft = offset + squareWidth * i;
            if (x >= hitLeft && x <= hitLeft + hitSize) {
                return i;
            }
        }
        return -1;
    }


    private void handleActionMove(TouchEvent event) {
        float radius = mPathWidth;
        mTempInvalidateRect.clear();
        boolean invalidateNow = false;
        float x = event.getPointerPosition(0).getX();
        float y = event.getPointerPosition(0).getY();

        y = y + diffYMove;
        x = x + diffXMove;
        LogUtil.error(this.getName(), "1=====TouchEvent=handleActionMove=x,y:" + x + "," + y + "," + screenHeight + "==" + screenWidth);
        Dot hitDot = detectAndAddHit(x, y);
        int patternSize = mPattern.size();
        if (hitDot != null && patternSize == 1) {
            mPatternInProgress = true;
            notifyPatternStarted();
        }
        // Note current x and y for rubber banding of in progress patterns
        float dx = Math.abs(x - mInProgressX);
        float dy = Math.abs(y - mInProgressY);
        if (dx > DEFAULT_DRAG_THRESHOLD || dy > DEFAULT_DRAG_THRESHOLD) {
            invalidateNow = true;
        }

        if (mPatternInProgress && patternSize > 0) {
            final ArrayList<Dot> pattern = mPattern;
            final Dot lastDot = pattern.get(patternSize - 1);
            float lastCellCenterX = getCenterXForColumn(lastDot.mColumn);
            float lastCellCenterY = getCenterYForRow(lastDot.mRow);

            // Adjust for drawn segment from last cell to (x,y). Radius
            // accounts for line width.
            float left = Math.min(lastCellCenterX, x) - radius;
            float right = Math.max(lastCellCenterX, x) + radius;
            float top = Math.min(lastCellCenterY, y) - radius;
            float bottom = Math.max(lastCellCenterY, y) + radius;

            // Invalidate between the pattern's new cell and the pattern's
            // previous cell
            if (hitDot != null) {
                float width = mViewWidth * 0.5f;
                float height = mViewHeight * 0.5f;
                float hitCellCenterX = getCenterXForColumn(hitDot.mColumn);
                float hitCellCenterY = getCenterYForRow(hitDot.mRow);

                left = Math.min(hitCellCenterX - width, left);
                right = Math.max(hitCellCenterX + width, right);
                top = Math.min(hitCellCenterY - height, top);
                bottom = Math.max(hitCellCenterY + height, bottom);
            }

            // Invalidate between the pattern's last cell and the previous
            // location
            mTempInvalidateRect.union(Math.round(left), Math.round(top),
                    Math.round(right), Math.round(bottom));
        }
        mInProgressX = event.getPointerPosition(0).getX();
        mInProgressY = event.getPointerPosition(0).getY();
        mInProgressY = mInProgressY + diffYMove;
        mInProgressX = mInProgressX + diffXMove;
        // To save updates, we only invalidate if the user moved beyond a
        // certain amount.
        if (invalidateNow) {
            mInvalidate.union(mTempInvalidateRect);
            mInvalidate.set(mTempInvalidateRect.left, mTempInvalidateRect.top, mTempInvalidateRect.right, mTempInvalidateRect.bottom);
            invalidate();
        }
    }

    private void sendAccessEvent(int resId) {
//        announceAccessibility("111111");
//        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
//            setContentDescription(getContext().getString(resId));
//            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
//            setContentDescription(null);
//        } else {
//            announceForAccessibility(getContext().getString(resId));
//        }
    }

    private void handleActionUp(TouchEvent event) {
        // Report pattern detected
        if (!mPattern.isEmpty()) {
            mPatternInProgress = false;
            release();
            cancelLineAnimations();
            notifyPatternDetected();
            invalidate();
        }
        if (PROFILE_DRAWING) {
            if (mDrawingProfilingStarted) {
//                Debug.stopMethodTracing();
                mDrawingProfilingStarted = false;
            }
        }
    }

    private void cancelLineAnimations() {
        for (int i = 0; i < sDotCount; i++) {
            for (int j = 0; j < sDotCount; j++) {
                DotState state = mDotStates[i][j];
                if (state.mLineAnimator != null) {
                    state.mLineAnimator.cancel();
                    state.mLineEndX = Float.MIN_VALUE;
                    state.mLineEndY = Float.MIN_VALUE;
                }
            }
        }
    }


    private void handleActionDown(TouchEvent event) {
        resetPattern();
        float x = event.getPointerPosition(0).getX();
        float y = event.getPointerPosition(0).getY();
        y = y + diffY;
        x = x + diffX;
        LogUtil.error(this.getName(), "1=====TouchEvent=handleActionDown=x,y:" + x + "," + y);
        Dot hitDot = detectAndAddHit(x, y);
        if (hitDot != null) {
            mPatternInProgress = true;
            mPatternViewMode = CORRECT;
            notifyPatternStarted();
        } else {
            mPatternInProgress = false;
            notifyPatternCleared();
        }
        if (hitDot != null) {
            float startX = getCenterXForColumn(hitDot.mColumn);
            float startY = getCenterYForRow(hitDot.mRow);

            float widthOffset = mViewWidth / 2f;
            float heightOffset = mViewHeight / 2f;

//            invalidate((int) (startX - widthOffset),
//                    (int) (startY - heightOffset),
//                    (int) (startX + widthOffset), (int) (startY + heightOffset));
            invalidate();
        }
        mInProgressX = x;
        mInProgressY = y;
        if (PROFILE_DRAWING) {
            if (!mDrawingProfilingStarted) {
//                Debug.startMethodTracing("PatternLockDrawing");
                mDrawingProfilingStarted = true;
            }
        }
    }

    private float getCenterXForColumn(int column) {
        return getPaddingLeft() + column * mViewWidth + mViewWidth / 2f;
    }

    private float getCenterYForRow(int row) {
        return getPaddingTop() + row * mViewHeight + mViewHeight / 2f;
    }

    private float calculateLastSegmentAlpha(float x, float y, float lastX,
                                            float lastY) {
        float diffX = x - lastX;
        float diffY = y - lastY;
        float dist = (float) Math.sqrt(diffX * diffX + diffY * diffY);
        float fraction = dist / mViewWidth;
        return Math.min(1f, Math.max(0f, (fraction - 0.3f) * 4f));
    }

    private Color getCurrentColor(boolean partOfPattern) {
        if (!partOfPattern || mInStealthMode || mPatternInProgress) {
            return new Color(mNormalStateColor);
        } else if (mPatternViewMode == WRONG) {
            return new Color(mWrongStateColor);
        } else if (mPatternViewMode == CORRECT
                || mPatternViewMode == AUTO_DRAW) {
            return new Color(mCorrectStateColor);
        } else {
            throw new IllegalStateException("Unknown view mode " + mPatternViewMode);
        }
    }

    private void drawCircle(Canvas canvas, float centerX, float centerY,
                            float size, boolean partOfPattern, float alpha) {
        mDotPaint.setColor(getCurrentColor(partOfPattern));
        mDotPaint.setAlpha((int) (alpha * 255));
        canvas.drawCircle(centerX, centerY, size / 2, mDotPaint);
    }

    /**
     * Represents a cell in the matrix of the pattern view
     */
    public static class Dot implements Serializable {

        private int mRow;
        private int mColumn;
        private static Dot[][] sDots;

        static {
            sDots = new Dot[sDotCount][sDotCount];

            // Initializing the dots
            for (int i = 0; i < sDotCount; i++) {
                for (int j = 0; j < sDotCount; j++) {
                    sDots[i][j] = new Dot(i, j);
                }
            }
        }

        private Dot(int row, int column) {
            checkRange(row, column);
            this.mRow = row;
            this.mColumn = column;
        }


        public int getId() {
            return mRow * sDotCount + mColumn;
        }

        public int getRow() {
            return mRow;
        }

        public int getColumn() {
            return mColumn;
        }

        public static synchronized Dot of(int row, int column) {
            checkRange(row, column);
            return sDots[row][column];
        }


        public static synchronized Dot of(int id) {
            return of(id / sDotCount, id % sDotCount);
        }

        private static void checkRange(int row, int column) {
            if (row < 0 || row > sDotCount - 1) {
                throw new IllegalArgumentException("mRow must be in range 0-"
                        + (sDotCount - 1));
            }
            if (column < 0 || column > sDotCount - 1) {
                throw new IllegalArgumentException("mColumn must be in range 0-"
                        + (sDotCount - 1));
            }
        }

        @Override
        public String toString() {
            return "(Row = " + mRow + ", Col = " + mColumn + ")";
        }

        @Override
        public boolean equals(Object object) {
            if (object instanceof Dot)
                return mColumn == ((Dot) object).mColumn
                        && mRow == ((Dot) object).mRow;
            return super.equals(object);
        }

        @Override
        public int hashCode() {
            int result = mRow;
            result = 31 * result + mColumn;
            return result;
        }
    }

    public static class DotState {
        float mScale = 1.0f;
        float mTranslateY = 0.0f;
        float mAlpha = 1.0f;
        float mSize;
        float mLineEndX = Float.MIN_VALUE;
        float mLineEndY = Float.MIN_VALUE;
        AnimatorValue mLineAnimator;
    }

}
